﻿using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using Sidvall.Serialization;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Sidvall.IO
{
    public class AzureFileSystem : Sidvall.IO.IStorage
    {
        #region Public Members

        public static int MaxBufferSize = 4 * 1024 * 1024; // A block may be up to 4 MB in size.

        #region StorageConnectionString

        private string _StorageConnectionString;
        public string StorageConnectionString
        {
            get
            {
                if (!string.IsNullOrWhiteSpace(_StorageConnectionString))
                    return _StorageConnectionString;
                return AzureSystemContext.Current.StorageConnectionString;
            }
            private set
            {
                _StorageConnectionString = value;
            }
        }

        #endregion
        #region DefaultBufferSize

        private int? _DefaultBufferSize;
        public int DefaultBufferSize
        {
            get
            {
                if (_DefaultBufferSize != null)
                    return _DefaultBufferSize.Value;
                return AzureSystemContext.Current.DefaultBufferSize;
            }
            set
            {
                if (value < 1)
                    _DefaultBufferSize = null;
                else if (value < MaxBufferSize)
                    _DefaultBufferSize = value;
                else
                    _DefaultBufferSize = MaxBufferSize;
            }
        }

        #endregion
        #region FolderDelimiter

        public string FolderDelimiter
        {
            get
            {
                return this.CloudBlobClient.DefaultDelimiter;
            }
        }

        #endregion
        #region ContainerName

        private string _ContainerName;
        public string ContainerName
        {
            get
            {
                return _ContainerName;
            }
            set
            {
                _ContainerName = value;
            }
        }

        #endregion

        #region Combine

        public string Combine(params string[] paths)
        {
            if (paths == null)
                return null;
            var sb = new System.Text.StringBuilder();
            foreach (var path in paths)
            {
                if (sb.Length > 0)
                    sb.Append(this.FolderDelimiter);
                sb.Append(path.Trim(this.FolderDelimiter, System.StringComparison.Ordinal));
            }
            return sb.ToString().Replace(new string[] { "\\", "/", System.IO.Path.DirectorySeparatorChar.ToString() }, this.FolderDelimiter, System.StringComparison.Ordinal);
        }

        #endregion
        #region GetFolderPath

        public string GetFolderPath(FolderType folder)
        {
            switch (folder)
            {
                case Sidvall.IO.FolderType.InstalledLocation:
                    return "";
                case Sidvall.IO.FolderType.LocalApplicationData:
                case Sidvall.IO.FolderType.RoamingApplicationData:
                    return "";
                case Sidvall.IO.FolderType.TemporaryApplicationData:
                    return "Temp";
                case Sidvall.IO.FolderType.Desktop:
                case Sidvall.IO.FolderType.MyDocuments:
                case Sidvall.IO.FolderType.MyMusic:
                case Sidvall.IO.FolderType.MyPictures:
                case Sidvall.IO.FolderType.MyVideos:
                    return "";
                default:
                    throw new System.ArgumentException(nameof(folder));
            }
        }

        #endregion
        #region GetPath

        /// <summary>
        /// Returns the full path for this storage:
        /// [BaseUri]/[path]
        /// </summary>
        public string GetPath(string path)
        {
            if (path != null)
                path = path.Replace(new string[] { "\\", "/", System.IO.Path.DirectorySeparatorChar.ToString() }, this.FolderDelimiter, System.StringComparison.Ordinal);
            if (string.IsNullOrWhiteSpace(path))
                return this.BaseUri;
            if (path.StartsWith(this.BaseUri))
                return path;
            return Combine(this.BaseUri, path);
        }

        #endregion
        #region GetStoragePath

        /// <summary>
        /// Returns the path that uniquely identifies the item to this storage:
        /// [path]
        /// </summary>
        public string GetStoragePath(string path)
        {
            if (path != null)
                path = path.Replace(new string[] { "\\", "/", System.IO.Path.DirectorySeparatorChar.ToString() }, this.FolderDelimiter, System.StringComparison.Ordinal);
            path = Sidvall.IO.StorageGroupManager.GetPathWithoutRoot(path, this.BaseUri, this.FolderDelimiter);
            return path;
        }

        #endregion

        #region CreateFolderAsync

        public Task<bool> CreateFolderAsync(string path, CancellationToken cancellationToken)
        {
            return Task.FromResult(true);
        }

        #endregion

        #region DeleteFileAsync

        public async Task<bool> DeleteFileAsync(string path, CancellationToken cancellationToken)
        {
            path = GetStoragePath(path);
            var item = await GetCloudBlockBlobAsync(path, cancellationToken).ConfigureAwait(false);
            return await item.DeleteIfExistsAsync(cancellationToken).ConfigureAwait(false);
        }

        #endregion
        #region DeleteFolderAsync

        public Task<bool> DeleteFolderAsync(string path, bool recursive, CancellationToken cancellationToken)
        {
            throw new System.NotImplementedException();
        }

        #endregion

        #region FileExistsAsync

        public async Task<bool> FileExistsAsync(string path, CancellationToken cancellationToken)
        {
            path = GetStoragePath(path);
            var item = await GetCloudBlockBlobAsync(path, cancellationToken).ConfigureAwait(false);
            return await item.ExistsAsync(cancellationToken).ConfigureAwait(false);
        }

        #endregion
        #region FolderExistsAsync

        public async Task<bool> FolderExistsAsync(string path, CancellationToken cancellationToken)
        {
            BlobContinuationToken continuationToken = null;

            path = GetStoragePath(path);
            var response = await GetBlobResultSegmentAsync(path, true, continuationToken, cancellationToken).ConfigureAwait(false);
            return response.Results.Any();
        }

        #endregion

        #region GetFileNamesAsync

        public async Task<IEnumerable<string>> GetFileNamesAsync(string path, string searchPattern, SearchOption searchOption,
            CancellationToken cancellationToken)
        {
            var items = new List<string>();
            path = GetStoragePath(path);
            BlobContinuationToken continuationToken = null;

            do
            {
                var response = await GetBlobResultSegmentAsync(path, true, continuationToken, cancellationToken).ConfigureAwait(false);
                continuationToken = response.ContinuationToken;
                foreach (var result in response.Results)
                    items.Add(result.Uri.ToString());
            }
            while (continuationToken != null);
            return items;
        }

        #endregion
        #region GetFolderNamesAsync

        public async Task<IEnumerable<string>> GetFolderNamesAsync(string path, string searchPattern, SearchOption searchOption,
             CancellationToken cancellationToken)
        {
            var index = new HashSet<string>();
            var items = new List<string>();
            path = GetStoragePath(path);
            BlobContinuationToken continuationToken = null;

            do
            {
                var response = await GetBlobResultSegmentAsync(path, true, continuationToken, cancellationToken).ConfigureAwait(false);
                continuationToken = response.ContinuationToken;
                foreach (var result in response.Results)
                {
                    var folder = result.Uri.ToString();
                    var pos = folder.LastIndexOf(this.FolderDelimiter);
                    folder = folder.Substring(0, pos);
                    if (!index.Contains(folder))
                    {
                        items.Add(folder);
                        index.Add(folder);
                    }
                }
            }
            while (continuationToken != null);
            return items;
        }

        #endregion
        #region GetFileSystemEntryNamesAsync

        public Task<IEnumerable<string>> GetFileSystemEntryNamesAsync(string path, string searchPattern, SearchOption searchOption,
            CancellationToken cancellationToken)
        {
            throw new System.NotImplementedException();
        }

        #endregion

        #region GetFileAsync

        public async Task<File> GetFileAsync(string fileName, bool loadFileDetails, CancellationToken cancellationToken)
        {
            fileName = GetStoragePath(fileName);
            var cloudBlockBlob = await GetCloudBlockBlobAsync(fileName, cancellationToken).ConfigureAwait(false);
            return await GetFileAsync(cloudBlockBlob, loadFileDetails, null, cancellationToken).ConfigureAwait(false);
        }
        private async Task<File> GetFileAsync(CloudBlockBlob cloudBlockBlob, bool loadDetails, System.Uri address, CancellationToken cancellationToken)
        {
            string path;

            if (cloudBlockBlob != null)
                path = cloudBlockBlob.Uri.ToString();
            else
                path = address.ToString();
            var item = new File()
            {
                FullName = path,
                StorageName = GetStoragePath(path),
                Name = System.IO.Path.GetFileName(path),
            };
            if (loadDetails)
            {
                if (cloudBlockBlob == null)
                    cloudBlockBlob = await GetCloudBlockBlobAsync(item.StorageName, cancellationToken).ConfigureAwait(false);
                await cloudBlockBlob.FetchAttributesAsync(cancellationToken).ConfigureAwait(false);
                item.Size = cloudBlockBlob.Properties.Length;
                if (cloudBlockBlob.Properties.LastModified != null)
                    item.LastWriteTime = cloudBlockBlob.Properties.LastModified.Value.UtcDateTime;
            }
            return item;
        }

        #endregion
        #region GetFilesAsync

        public async Task<IEnumerable<File>> GetFilesAsync(string path, string searchPattern, SearchOption searchOption, bool loadFileDetails,
             CancellationToken cancellationToken)
        {
            var items = new List<File>();
            path = GetStoragePath(path);
            BlobContinuationToken continuationToken = null;

            do
            {
                var response = await GetBlobResultSegmentAsync(path, true, continuationToken, cancellationToken).ConfigureAwait(false);
                continuationToken = response.ContinuationToken;
                foreach (var result in response.Results)
                {
                    var item = await GetFileAsync(null, loadFileDetails, result.Uri, cancellationToken).ConfigureAwait(false);
                    items.Add(item);
                }
            }
            while (continuationToken != null);
            return items;
        }

        #endregion
        #region GetFolderAsync

        public Task<Folder> GetFolderAsync(string folderName, bool loadFolderDetails, CancellationToken cancellationToken)
        {
            throw new System.NotImplementedException();
        }

        #endregion
        #region GetFoldersAsync

        public async Task<IEnumerable<Folder>> GetFoldersAsync(string path, string searchPattern, SearchOption searchOption, bool loadFolderDetails,
             CancellationToken cancellationToken)
        {
            var index = new HashSet<string>();
            var items = new List<Folder>();
            path = GetStoragePath(path);
            BlobContinuationToken continuationToken = null;

            do
            {
                var response = await GetBlobResultSegmentAsync(path, true, continuationToken, cancellationToken).ConfigureAwait(false);
                continuationToken = response.ContinuationToken;
                foreach (var result in response.Results)
                {
                    var folder = result.Uri.ToString();
                    var pos = folder.LastIndexOf(this.FolderDelimiter);
                    folder = folder.Substring(0, pos);
                    if (!index.Contains(folder))
                    {
                        var item = GetFolder(loadFolderDetails, folder);
                        items.Add(item);
                        index.Add(folder);
                    }
                }
            }
            while (continuationToken != null);
            return items;
        }

        #endregion

        #region OpenFileAsync

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        public async Task<System.IO.Stream> OpenFileAsync(string path, FileMode mode, FileAccess access, CancellationToken cancellationToken)
        {
            path = GetStoragePath(path);
            var item = new AzureStream()
            {
                AzureFileSystem = this,
                CloudBlob = await GetCloudBlockBlobAsync(path, cancellationToken).ConfigureAwait(false),
            };
            if (await item.CloudBlob.ExistsAsync(cancellationToken).ConfigureAwait(false))
            {
                await item.CloudBlob.FetchAttributesAsync(cancellationToken).ConfigureAwait(false);
                item.SetLength(item.CloudBlob.Properties.Length);
            }
            else
            {
                item.SetLength(0);
            }
            return item;
        }

        #endregion

        #region CopyFileAsync

        public Task<bool> CopyFileAsync(string sourceFileName, string destinationFileName, bool deleteExistingFile, CancellationToken cancellationToken)
        {
            throw new System.NotImplementedException();
        }

        #endregion
        #region MoveFileAsync

        public Task<bool> MoveFileAsync(string sourceFileName, string destinationFileName, bool deleteExistingFile, CancellationToken cancellationToken)
        {
            throw new System.NotImplementedException();
        }

        #endregion
        #region MoveFolderAsync

        public Task<bool> MoveFolderAsync(string sourceFolderName, string destinationFolderName, bool deleteExistingFolder, CancellationToken cancellationToken)
        {
            throw new System.NotImplementedException();
        }

        #endregion

        #endregion
        #region Private Members

        #region BaseUri

        private string BaseUri
        {
            get
            {
                if (string.IsNullOrWhiteSpace(this.ContainerName))
                    return this.CloudBlobClient.BaseUri.ToString();
                return Combine(this.CloudBlobClient.BaseUri.ToString(), this.ContainerName);
            }
        }

        #endregion
        #region CloudBlobClient

        private System.Lazy<CloudBlobClient> _CloudBlobClient;
        internal CloudBlobClient CloudBlobClient
        {
            get
            {
                return _CloudBlobClient.Value;
            }
        }

        #endregion

        #region GetBlobResultSegmentAsync

        private async Task<BlobResultSegment> GetBlobResultSegmentAsync(string path, bool useFlatBlobListing, BlobContinuationToken continuationToken, CancellationToken cancellationToken)
        {
            var container = GetCloudBlobContainer(ref path);
            await container.CreateIfNotExistsAsync(cancellationToken).ConfigureAwait(false);
            return await container.ListBlobsSegmentedAsync(path, useFlatBlobListing, BlobListingDetails.None, null, continuationToken, null, null, cancellationToken).ConfigureAwait(false);
        }

        #endregion
        #region GetCloudBlockBlobAsync

        private async Task<CloudBlockBlob> GetCloudBlockBlobAsync(string path, CancellationToken cancellationToken)
        {
            var container = GetCloudBlobContainer(ref path);
            await container.CreateIfNotExistsAsync(cancellationToken).ConfigureAwait(false);
            return container.GetBlockBlobReference(path);
        }

        #endregion
        #region GetCloudBlobClient

        private CloudBlobClient GetCloudBlobClient()
        {
            var account = CloudStorageAccount.Parse(this.StorageConnectionString);
            return account.CreateCloudBlobClient();
        }

        #endregion
        #region GetCloudBlobContainer

        private CloudBlobContainer GetCloudBlobContainer(ref string path)
        {
            if (!string.IsNullOrWhiteSpace(this.ContainerName))
                return this.CloudBlobClient.GetContainerReference(this.ContainerName);
            var pos = path.IndexOf(this.CloudBlobClient.DefaultDelimiter);
            if (pos >= 0)
            {
                var container = this.CloudBlobClient.GetContainerReference(path.Substring(0, pos + 1));
                path = path.Substring(pos + 1);
                return container;
            }
            else
            {
                return this.CloudBlobClient.GetContainerReference("default");
            }
        }

        #endregion
        #region GetFolder

        private Folder GetFolder(bool loadDetails, string path)
        {
            var item = new Folder()
            {
                FullName = path,
                StorageName = GetStoragePath(path),
                Name = System.IO.Path.GetDirectoryName(path),
            };
            return item;
        }

        #endregion

        #endregion
        #region Constructors

        public AzureFileSystem()
        {
            _CloudBlobClient = new System.Lazy<CloudBlobClient>(GetCloudBlobClient);
        }
        public AzureFileSystem(string storageConnectionString)
        {
            this.StorageConnectionString = storageConnectionString;
            _CloudBlobClient = new System.Lazy<CloudBlobClient>(GetCloudBlobClient);
        }
        public AzureFileSystem(string storageConnectionString, string containerName)
        {
            this.ContainerName = containerName;
            this.StorageConnectionString = storageConnectionString;
            _CloudBlobClient = new System.Lazy<CloudBlobClient>(GetCloudBlobClient);
        }

        #endregion
    }
}
